#ifndef SIMPLE_SUPPORT_ITERATOR_MATCH_HPP
#define SIMPLE_SUPPORT_ITERATOR_MATCH_HPP

#include <iterator> // std::input_iterator_tag
#include <type_traits> // std::enable_if_t std::is_same_v
#include <utility> // std::forward
#include <cstddef> // std::nullptr_t

#include "../function_utils.hpp" // support::invoke

namespace simple::support
{

	template <typename Matcher, typename = std::nullptr_t>
	struct match_state
	{
		bool matched_;
		bool matched(const Matcher&) const { return matched_; }
		void update(bool accepted, const Matcher&) { matched_ |= accepted; }
	};

	template <typename Matcher>
	struct match_state<Matcher, decltype(std::declval<Matcher>()(), nullptr)>
	{
		bool matched(const Matcher& matcher) const { return matcher(); }
		void update(bool, const Matcher&) { /* no-op */ }
	};

	template <typename Matcher>
	struct match_iterator
	{
		using value_type = void; // predicate param type?
		using difference_type = void;
		using pointer = void;
		using reference = void;
		using iterator_category = std::input_iterator_tag;
		Matcher matcher;
		match_state<Matcher> state;

		template <typename F,
			std::enable_if_t<std::is_same_v<F,Matcher>>* = nullptr>
		explicit match_iterator(F&& matcher)
			: matcher(std::forward<F>(matcher)), state{}
		{}

		match_iterator& operator++() { return *this; }
		match_iterator operator++(int) { match_iterator prev = *this; ++(*this); return prev; }

		friend bool operator==(const match_iterator& one, const match_iterator& other)
		{
			return one.state.matched(one.matcher) == other.state.matched(other.matcher);
		}
		friend bool operator!=(const match_iterator& one, const match_iterator& other)
		{
			return !(one == other);
		}

		class proxy
		{
			Matcher& matcher;
			match_state<Matcher>& state;

			proxy(Matcher& matcher, match_state<Matcher>& state) : matcher(matcher), state(state) {}
			friend struct match_iterator;
			public:
			proxy() = delete;
			proxy(const proxy&) = delete;
			proxy(const proxy&&) = delete;
			proxy& operator=(const proxy&) = delete;
			proxy& operator=(const proxy&&) = delete;

			template <typename T, std::enable_if_t<not std::is_same_v<T,proxy>>* = nullptr>
			bool operator==(const T& x) const
			{
				auto accepted = support::invoke(matcher, x);
				state.update(accepted,matcher);
				return accepted;
			}
			// NOTE: clever c++20 makes this into an infinite loop roflmao, thankfully clever g++ 10 warned me
			// > asks you to boilerplate
			// > punishes you for boilerplate
			// template <typename T, std::enable_if_t<not std::is_same_v<T,proxy>>* = nullptr>
			// friend bool operator==(const T& x, const proxy& p)
			// {
			// 	return p == x;
			// }

		};

		const proxy operator*()
		{
			return {matcher, state};
		}
	};
	template <typename F> match_iterator(F&&) -> match_iterator<F>;

} // namespace simple::support

#endif /* end of include guard */
